package cz.vse.integrace.iag;

import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAuthorizationRequestUrl;
import cz.vse.integrace.iag.db.Adresa;
import cz.vse.integrace.iag.db.ZamestnanciDAO;
import cz.vse.integrace.iag.db.Oddeleni;
import cz.vse.integrace.iag.db.Zamestnanec;
import cz.vse.integrace.iag.synchronizace.Synchronizace;
import cz.vse.integrace.iag.google.OAuthNastaveni;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet starající se o požadavky zadávané na integraci.
 * Umožňuje vkládání dat, plánování a spouštění synchronizace, 
 * zobrazení dat z databáze, zobrazení nápovědy.
 *
 * @author David Nejedly
 * @version 1.0
 */
public class IntegraceServlet extends HttpServlet {

    private static final String ZAM_ID = "zamId";
    @EJB
    private ZamestnanciDAO zamestnaciDAO;
    @EJB
    private IntegraceTimerBean integraceTimerBean;
    @EJB
    private Synchronizace synchronizace;
    private static final long TIMEOUT = 2 * 60 * 1000;
    private static final String INTEGRACE_JSP = "integrace.jsp";
    private static final Logger LOGGER = Logger.getLogger(IntegraceServlet.class.getName());

    /**
     * Metoda zpracovavající requesty přicházející metodou get.
     * Výkon požadavku je proveden metodou performAction.
     *
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        String action = request.getParameter("action");
        performAction(request, response, action);
    }

    /**
     * Metoda zpracovavající requesty přicházející metodou post.
     * Výkon požadavku je proveden metodou performAction.
     *
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        String action = request.getParameter("action");
        performAction(request, response, action);
    }

    /**
     * Větvení na základě akce.
     *
     * @param action akce, která se má provédst
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void performAction(HttpServletRequest request, HttpServletResponse response, String action) throws ServletException, IOException {
        // V případě, že nebyla specifikována akce,
        // zobrazíme hlavní stránku (integrace.jsp).
        if ("".equals(action) || action == null) {
            destroyIntegracePageBean(request.getSession());
            initializeIntegracePageBean(request.getSession());
            request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
            return;
        }

        // Větvení podle akce
        if ("novyToken".equals(action)) {
            doNovyToken(request, response);
        } else if ("novyZam".equals(action)) {
            doNovyZamestnanec(request, response);
        } else if ("integrace".equals(action)) {
            doIntegrace(request, response);
        } else if ("synchronizace".equals(action)) {
            doSynchronizace(request, response);
        } else if ("novyZamPridani".equals(action)) {
            doNovyZamestnanecPridani(request, response);
        } else if ("stav".equals(action)) {
            doStav(request, response);
        } else if ("zamestnanci".equals(action)) {
            doZamestnanci(request, response);
        } else if ("noveOdd".equals(action)) {
            doNoveOddeleni(request, response);
        } else if ("noveOddPridani".equals(action)) {
            doNoveOddeleniPridani(request, response);
        } else if ("naplanovat".equals(action)) {
            doNaplanovat(request, response);
        } else if ("napoveda".equals(action)) {
            doNapoveda(request, response);
        } else if ("prubehSynch".equals(action)) {
            doPrubehSynchronizace(request, response);
        }
    }

    /**
     * Metoda, která odebere se sessiony page bean.
     * 
     * @param session session ze které je odebrán page bean
     */
    private void destroyIntegracePageBean(HttpSession session) {
        session.removeAttribute("integraceBean");
    }

    /**
     * Metoda vytvářející page bean pro aktuální session.
     * 
     * @param session aktuální session
     * @return nový page bean pro integrace.jsp
     */
    private IntegracePageBean initializeIntegracePageBean(HttpSession session) {
        IntegracePageBean integracePageBean = new IntegracePageBean();
        integracePageBean.setObsah(Obsahy.STAV);
        integracePageBean.setOznameni("Vítejte!");
        integracePageBean.setPodrobneInfo("Aplikace provádí synchronizaci Google kontaků s SQL datatabází.<br/>"
                + "Přístup do Google kontaktů může být uskutečněn pomocí emailu a hesla,<br/>"
                + "nebo může být přístup na základě OAuth 2.0 tokenu, který je možné získat<br/>"
                + "v nabídce Zaměstnaci. <br/>"
                + "Jednotliví zaměstnanci mohou být přidávání, buď přes webové rozhraní <br/>"
                + "v nabíce Nový zaměstnanec, nebo vloženi přímo do databáze.<br/>"
                + "Podrobnější nápověda je v sekci Nápověda.");
        session.setAttribute("integraceBean", integracePageBean);
        return integracePageBean;
    }

    /**
     * Metoda, která přesměruje na google autorizační server
     *
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNovyToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        Zamestnanec zamestnanec = integracePageBean.getZamestnanec(new Integer(request.getParameter(ZAM_ID)));
        if (zamestnanec != null) {
            request.getSession().setAttribute("email", zamestnanec.getEmailPrace());
            request.getSession().setAttribute(ZAM_ID, zamestnanec.getId());
        }
        // Redirect to the Google OAuth 2.0 authorization endpoint
        response.sendRedirect(new GoogleAuthorizationRequestUrl(OAuthNastaveni.getClientId(),
                OAuthCodeCallbackServlet.getOAuthCodeCallbackUrl(request), OAuthNastaveni.getScopesAsString()).build());
        return;
    }

    /**
     * Construct the request's URL without the parameter part.
     *
     * @param req the HttpRequest object
     * @return The constructed request's URL
     */
    public static String getFullRequestUrl(HttpServletRequest req) {
        String scheme = req.getScheme() + "://";
        String serverName = req.getServerName();
        String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
        String contextPath = req.getContextPath();
        String servletPath = req.getServletPath();
        String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
        String queryString = (req.getQueryString() == null) ? "" : "?" + req.getQueryString();
        return scheme + serverName + serverPort + contextPath + servletPath + pathInfo + queryString;
    }

    /**
     * Metoda provede zobrazení obrazovky se zadáváním nového zaměstnance.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNovyZamestnanec(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setOddeleni(zamestnaciDAO.getOddeleni());
        integracePageBean.setObsah(Obsahy.NOVY_ZAMESTNANEC);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda, která provede asynchroní spuštění synchronizace mezi google
     * kontakty a SQL databáze.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doIntegrace(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        final IntegracePageBean integracePageBean = getIntegracePageBean(request);
        final AsyncContext ac = request.startAsync();
        ac.setTimeout(TIMEOUT);
        ac.addListener(new AsyncListener() {

            @Override
            public void onComplete(AsyncEvent event) throws IOException {
                LOGGER.log(Level.FINEST, "Asynchroní synchronizace dokončena.");
            }

            @Override
            public void onTimeout(AsyncEvent event) throws IOException {
                integracePageBean.setOznameni("Nastal timeout.");
                integraceTimerBean.scheduleDate(new Date(System.currentTimeMillis() + 1000));
                integracePageBean.setZobrazPostup(false);
                integracePageBean.setPodrobneInfo("Synchronizace se nezdařila. Proto byla spuštěna znovu na pozadí.");
            }

            @Override
            public void onError(AsyncEvent event) throws IOException {
                integracePageBean.setOznameni("Nastala chyba.");
                integraceTimerBean.scheduleDate(new Date(System.currentTimeMillis() + 1000));
                integracePageBean.setZobrazPostup(false);
                integracePageBean.setPodrobneInfo("Synchronizace se nezdařila. Proto byla spuštěna znovu na pozadí.");

            }

            @Override
            public void onStartAsync(AsyncEvent event) throws IOException {
                LOGGER.log(Level.FINEST, "Spuštěna asynchroni synchronizace.");
            }
        });
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
        executor.execute(new AsynchroniSynchronizace(ac, integracePageBean));
        integracePageBean.setOznameni("Spuštěna asynchroní synchronizace.");
        integracePageBean.setZobrazPostup(true);
        integracePageBean.setPodrobneInfo("Probíhá synchronice . . . "
                + "<br/>Tato operace může trvat delší dobu."
                + "<br/>Pořadavek trvajcí déle než 2 minuty je ukončen a spuštěn na pozadí přes Timer.");
        integracePageBean.setObsah(Obsahy.STAV);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda zobrazí obrazovku pro spuštění nebo naplánování synchronizace.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doSynchronizace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setZobrazPostup(false);
        integracePageBean.setPodrobneInfo("");
        integracePageBean.setObsah(Obsahy.SYNCHRONIZACE);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda, která přečte informace z formuláře a založí nového zaměstnance 
     * v databázi.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNovyZamestnanecPridani(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        Zamestnanec zamestnanec = new Zamestnanec();
        zamestnanec.setJmeno(request.getParameter("jmeno"));
        zamestnanec.setPrijmeni(request.getParameter("prijmeni"));
        zamestnanec.setPozice(request.getParameter("pozice"));
        zamestnanec.setHrubaMzda(Double.parseDouble(request.getParameter("hrubaMzda")));
        zamestnanec.setEmailPrace(request.getParameter("emailPrace"));
        Adresa adresa = new Adresa();
        adresa.setUlice(request.getParameter("ulicePrace"));
        adresa.setCisloPopisne(Integer.parseInt(request.getParameter("cisloPopisnePrace")));
        adresa.setMesto(request.getParameter("mestoPrace"));
        adresa.setPsc(Integer.parseInt(request.getParameter("pscPrace")));
        zamestnanec.setPrace(adresa);
        zamestnanec.setHeslo(request.getParameter("heslo"));
        String nazevOddeleni = request.getParameter("odd");
        for (Oddeleni oddeleni : integracePageBean.getOddeleni()) {
            if (oddeleni.getNazev().equals(nazevOddeleni)) {
                zamestnanec.setOddeleni(oddeleni);
                break;
            }
        }
        if (zamestnaciDAO.ulozZamestnance(zamestnanec)) {
            integracePageBean.setOznameni("Úspěšně uloženo.");
        } else {
            integracePageBean.setOznameni("Uložení se nezdařilo. Prosím opakujte.");
        }
        integracePageBean.setZobrazPostup(false);
        integracePageBean.setPodrobneInfo("");
        integracePageBean.setObsah(Obsahy.STAV);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda, která zobrazí stav aplikace.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doStav(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setZobrazPostup(false);
        integracePageBean.setPodrobneInfo("");
        integracePageBean.setObsah(Obsahy.STAV);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda, která najde integraceBean, pokud není nalezen, tak se vytvoří
     * nový.
     * 
     * @param request požadavek
     * @return nalezený integraceBean nebo nový
     * @throws ServletException
     */
    private IntegracePageBean getIntegracePageBean(HttpServletRequest request) throws ServletException {
        IntegracePageBean integraceBean = (IntegracePageBean) request.getSession(true).getAttribute("integraceBean");
        if (integraceBean == null) {
            return initializeIntegracePageBean(request.getSession());
        }
        return integraceBean;
    }

    /**
     * Metoda, které zobrací zaměstnance z databáze a je možné ověřit vybraného 
     * zaměstnace pomocí OAuth 2.0.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doZamestnanci(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setZamestnanci(zamestnaciDAO.getZamestnance());
        integracePageBean.setObsah(Obsahy.ZAMESTNANCI);
        integracePageBean.setZobrazPostup(false);
        integracePageBean.setPodrobneInfo("");
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Zobrazí formulář pro přidání nového oddělení.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNoveOddeleni(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setZamestnanci(zamestnaciDAO.getZamestnance());
        integracePageBean.setObsah(Obsahy.NOVE_ODDELENI);
        integracePageBean.setZobrazPostup(false);
        integracePageBean.setPodrobneInfo("");
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Uloží nové oddělení z formuláře do databáze.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNoveOddeleniPridani(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        Oddeleni oddeleni = new Oddeleni();
        oddeleni.setNazev(request.getParameter("nazev"));
        String zamId = request.getParameter(ZAM_ID);
        if (!"null".equals(zamId)) {
            oddeleni.setVedouci(integracePageBean.getZamestnanec(Integer.parseInt(zamId)));
        }
        if (zamestnaciDAO.ulozOddeleni(oddeleni)) {
            integracePageBean.setOznameni("Úspěšně uloženo.");
        } else {
            integracePageBean.setOznameni("Uložení se nezdařilo. Prosím opakujte.");
        }
        integracePageBean.setObsah(Obsahy.STAV);
        integracePageBean.setZobrazPostup(false);
        integracePageBean.setPodrobneInfo("");
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda, která naplánuje spouštění synchronizace podle údajů zadaných 
     * v formuláři.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNaplanovat(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        String typ = request.getParameter("typ");
        if (typ.equals("cron")) {
            String cron = request.getParameter("cron");
            integraceTimerBean.scheduleCron(cron.split(" "));
            integracePageBean.setOznameni("Synchronizace naplánována.");
            integracePageBean.setPodrobneInfo("");
        } else if (typ.equals("vyber")) {
            String[] cron = new String[7];
            cron[0] = request.getParameter("second");
            cron[1] = request.getParameter("minute");
            cron[2] = request.getParameter("hour");
            cron[3] = getWeekDay(request.getParameter("weekDay"));
            cron[4] = request.getParameter("monthDay");
            cron[5] = request.getParameter("month");
            cron[6] = request.getParameter("year");
            integraceTimerBean.scheduleCron(cron);
            integracePageBean.setOznameni("Synchronizace naplánována.");
            integracePageBean.setPodrobneInfo("");
        } else {
            integracePageBean.setOznameni("Naplánování se nezdařilo, prosím opakujte");
        }
        integracePageBean.setObsah(Obsahy.STAV);
        integracePageBean.setZobrazPostup(false);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);

    }

    /**
     * Převod z českého výrazu do anglického u dne v týdnu.
     *
     * @param denVTydnu den v týdnu česky
     * @return den v týdnu anglicky
     */
    private String getWeekDay(String denVTydnu) {
        if (denVTydnu.equals("*") || denVTydnu.equals("?")) {
            return denVTydnu;
        } else if (denVTydnu.equals("Pondělí")) {
            return "MON";
        } else if (denVTydnu.equals("Úterý")) {
            return "TUE";
        } else if (denVTydnu.equals("Středa")) {
            return "WEN";
        } else if (denVTydnu.equals("Čtvrtek")) {
            return "THU";
        } else if (denVTydnu.equals("Pátek")) {
            return "FRI";
        } else if (denVTydnu.equals("Sobota")) {
            return "SAT";
        } else if (denVTydnu.equals("Neděle")) {
            return "SUN";
        }
        return "?";
    }

    /**
     * Metoda, která zobrazí nápovědu.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doNapoveda(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setObsah(Obsahy.NAPOVEDA);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Metoda, která je volána, když probíhá asynchroní synchronizace,
     * slouží k zobrazování progress baru.
     * 
     * @param request požadavek
     * @param response odpověd
     * @throws ServletException vyhazováné pokud dojede k chybě
     * @throws IOException vyhozováné pokud se nepodaří najít nějaký soubor
     */
    private void doPrubehSynchronizace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        IntegracePageBean integracePageBean = getIntegracePageBean(request);
        integracePageBean.setObsah(Obsahy.STAV);
        request.getRequestDispatcher(INTEGRACE_JSP).forward(request, response);
    }

    /**
     * Třída zajišťující asynchroní provedení synchronizace.
     *
     * @author David Nejedlý
     * @version 1.0
     */
    class AsynchroniSynchronizace implements Runnable {

        AsyncContext ac;
        private final IntegracePageBean integracePageBean;

        public AsynchroniSynchronizace(AsyncContext ac, final IntegracePageBean integracePageBean) {
            this.integracePageBean = integracePageBean;
            this.ac = ac;
            LOGGER.warning("Spuštěna asynchroní synchronizace.");
        }

        /**
         * Spouští synchronizaci.
         */
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            LOGGER.log(Level.INFO, "Synchronizace spu\u0161t\u011bna v {0}.", new Date(start).toString());
            boolean vysledekSynchronizace = synchronizace.synchronizace();

            if (vysledekSynchronizace) {
                integracePageBean.setOznameni("Úspěšně synchronizováno.");
            } else {
                integracePageBean.setOznameni("Synchronizace se nezdařila. Prosím opakujte.");
            }
            integracePageBean.setZobrazPostup(false);
            long trvani = System.currentTimeMillis() - start;
            integracePageBean.setPodrobneInfo("Doba trvání byla " + trvani / 1000 + " s.");
            ac.complete();
        }
    }
}